﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Xml;
using CodeParser;
using DirectXMetaData;
using EffectEd.CodeCompletion;
using HlslCompiler;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
using SlimDX.D3DCompiler;

namespace EffectEd
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

        private SearchWindow _searchWindow = null;
        private readonly DocumentController _documentController;
        private Settings _settings;
        private CodeAssistance _codeAssistance;

        public static RoutedCommand SaveCommand = new RoutedCommand();
        public static RoutedCommand FindCommand = new RoutedCommand();
        public static RoutedCommand CompileCommand = new RoutedCommand();

        public MainWindow()
        {
            InitializeComponent();
            _documentController = new DocumentController();
            LoadSettings();
            LoadCommands();
            hlslEdit.TextArea.Caret.PositionChanged += CaretOnPositionChanged;
            hlslEdit.TextChanged += CaretOnPositionChanged;
        }

        private void CaretOnPositionChanged(object sender, EventArgs eventArgs)
        {
            statusBar.Background = Brushes.Transparent;
            statusBar.Items[0] = GetUserFriendlyHierarchy().Replace(".", " > ");
        }

        private void LoadCommands()
        {
            SaveCommand.InputGestures.Add(new KeyGesture(Key.S, ModifierKeys.Control, "Ctrl+S"));
            FindCommand.InputGestures.Add(new KeyGesture(Key.F, ModifierKeys.Control, "Ctrl+F"));
            CompileCommand.InputGestures.Add(new KeyGesture(Key.F5, ModifierKeys.None, "F5"));
            CompileCommand.InputGestures.Add(new KeyGesture(Key.F6, ModifierKeys.None, "F6"));
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            try
            {
                InitializeDocumentController();
                ProcessStartupArguments();
                InitializeHlslEditor();
                InitializeCodeAssistance();
                ConfigureCodeCompletion();

                AllowDrop = true;
            }
            catch (Exception exception)
            {
                MessageBox.Show(exception.Message, "Startup error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private void LoadSettings()
        {
            _settings = null;
            if (!TryLoadSettings())
            {
                MessageBox.Show(@"Could not load settings file ""\data\settings.xml"". Using default.", "Settings",
                                    MessageBoxButton.OK, MessageBoxImage.Error);
                _settings = GetDefaultSettings();
            }
            ApplySettings();
            RestoreWindowState();
        }

        private void RestoreWindowState()
        {
            Width = _settings.WindowWidth;
            Height = _settings.WindowHeight;
            WindowState = _settings.WindowState;
        }

        private void ApplySettings()
        {
            hlslEdit.ShowLineNumbers = _settings.ShowLineNumbers;
        }

        private bool TryLoadSettings()
        {
            if (File.Exists(DataFiles.GetAbsolutePath("settings.xml")))
            {
                try
                {
                    _settings = Settings.Load(DataFiles.GetAbsolutePath("settings.xml"));
                }
                    // ReSharper disable EmptyGeneralCatchClause
                catch (Exception)
                    // ReSharper restore EmptyGeneralCatchClause
                {
                    // eat it, fallback plan below
                }
            }
            return _settings != null;
        }

        private static Settings GetDefaultSettings()
        {
            return new Settings()
                       {
                           ShowLineNumbers = true,
                           AlwaysSaveOnClose = false,
                           WindowWidth = 1024,
                           WindowHeight = 768,
                           WindowState = WindowState.Normal,
                           DefaultCompilerSettings = new CompilerSettings()
                                                         {
                                                             Direct3D = "10",
                                                             Profile = "fx_4_0",
                                                             ShaderFlags = ShaderFlags.WarningsAreErrors
                                                         }
                       };
        }

        private void InitializeCodeAssistance()
        {
            var absolutePath = DataFiles.GetAbsolutePath("CodeAssistance.xml");
            if (File.Exists(absolutePath))
            {
                try
                {
                    _codeAssistance = CodeAssistance.Load(absolutePath);
                }
                catch (Exception e)
                {
                    MessageBox.Show(@"Could not load ""\data\CodeAssistance.xml"": " + e.Message, "No code assistance", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        private void ConfigureCodeCompletion()
        {
            try
            {
                var codeCompletionProvider = new CodeCompletionProvider();
                codeCompletionProvider.CodeAssistanceConfigurationRequested += CodeAssistanceConfigurationRequested;
                hlslEdit.CodeCompletionProvider = codeCompletionProvider;
            }
            catch (Exception e)
            {
                MessageBox.Show("Code completion failed to initialize.");
            }
        }

        private void CodeAssistanceConfigurationRequested(object sender, CodeAssistanceConfigurationEventArgs e)
        {
            var compilerSettings = GetCompilerSettings();
            var direct3D = _codeAssistance.Versions.SingleOrDefault(v => v.Version == compilerSettings.Direct3D);
            if (direct3D == null) throw new Exception(string.Format("No code assistance found for Direct3D version {0}", compilerSettings.Direct3D));
            var profile = direct3D.CompileProfiles.SingleOrDefault(p => p.Name == compilerSettings.Profile);
            if (profile == null) throw new Exception(string.Format("No code assistance found for Direct3D version {0}, Profile {1}", compilerSettings.Direct3D, compilerSettings.Profile));
            e.Configuration =
                _codeAssistance.CodeAssistanceConfigurations.SingleOrDefault(
                    c => c.Name == profile.CodeAssistanceConfiguration);
            if (e.Configuration == null)
                throw new Exception(string.Format(
                        "Code assistance configuration with name \"{0}\" referred by Direct3D version {1}, Profile {2}, does not exist.",
                        profile.CodeAssistanceConfiguration, direct3D.Version, profile.Name));
        }

        private void InitializeDocumentController()
        {
            _documentController.FileNameChanged += FileNameChanged;
            _documentController.ContentRequested += ContentRequested;
            _documentController.ContentSet += ContentSet;
            _documentController.ModifiedChanged += ModifiedChanged;
        }

        private void ModifiedChanged(object sender, EventArgs eventArgs)
        {
            UpdateTitle();
        }

        private void ContentSet(object sender, ContentEventArgs e)
        {
            hlslEdit.Text = e.Content;
        }

        private void ContentRequested(object sender, ContentEventArgs e)
        {
            e.Content = hlslEdit.Text;
        }

        private void InitializeHlslEditor()
        {
            using (var reader = new XmlTextReader(DataFiles.GetAbsolutePath("hlsl10.xshd")))
            {
                hlslEdit.SyntaxHighlighting = HighlightingLoader.Load(reader, HighlightingManager.Instance);
            }
            hlslEdit.TextChanged += textEditor_TextChanged;
            hlslEdit.Focus();
        }

        private void ProcessStartupArguments()
        {
            var startupArguments = ((App)Application.Current).StartupArguments;
            if (startupArguments.Length > 0 && File.Exists(startupArguments[0]))
            {
                _documentController.Load(startupArguments[0]);
            }
        }

        private void FileNameChanged(object sender, EventArgs eventArgs)
        {
            UpdateTitle();
        }

        private void buttonNew_Click(object sender, RoutedEventArgs e)
        {
            _documentController.New();
        }

        private string GetTitle()
        {
            if (_documentController.FileName == null)
            {
                return "EffectEd - <new file>";
            }
            return string.Format("EffectEd - {0} {1}", Path.GetFileName(_documentController.FileName),
                                 _documentController.Modified ? "*" : string.Empty);
        }

        private void UpdateTitle()
        {
            Title = GetTitle();
        }

        private void buttonSave_Click(object sender, RoutedEventArgs e)
        {
            _documentController.Save();
        }

        private void textEditor_TextChanged(object sender, System.EventArgs e)
        {
            if (_documentController.Modified) return;
            _documentController.Modified = true;
            UpdateTitle();
        }

        private void buttonLoad_Click(object sender, RoutedEventArgs e)
        {
            _documentController.Load();
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (_settings.AlwaysSaveOnClose && _documentController.FileName != null)
            {
                if (!_documentController.Save())
                {
                    e.Cancel = true;
                }
            }
            else if (!_documentController.CheckAndSaveModifications())
            {
                e.Cancel = true;
            }
            if (!e.Cancel)
            {
                _settings.WindowHeight = (int)Height;
                _settings.WindowWidth = (int)Width;
                _settings.WindowState = WindowState;
                SaveSettings();
            }
        }

        private void ButtonCompile_Click(object sender, RoutedEventArgs e)
        {
            Compile();
        }

        private void Compile()
        {
            var feedback = Compiler.Compile(hlslEdit.Text, GetCompilerSettings());
            ShowFeedback(feedback, "Compile succeeded", "Compile failed");
        }

        private CompilerSettings GetCompilerSettings()
        {
            return CompilerSettingsParser.GetCompilerSettings(hlslEdit.Text) ?? _settings.DefaultCompilerSettings;
        }

        private void ShowFeedback(List<Feedback> feedback, string feedbackEmptyMessage, string feedbackNotEmptyMessage)
        {
            if (feedback != null && feedback.Count > 0)
            {
                listViewMessages.ItemsSource = feedback;
                listViewMessages.Visibility = Visibility.Visible;
                statusBar.Items[0] = feedbackNotEmptyMessage;
                statusBar.Background = new SolidColorBrush(Color.FromArgb(50, 255, 0, 0));
            }
            else
            {
                listViewMessages.Visibility = Visibility.Collapsed;
                statusBar.Background = new SolidColorBrush(Color.FromArgb(50, 0, 255, 0));
                statusBar.Items[0] = feedbackEmptyMessage;
            }
        }

        private void listViewMessages_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (listViewMessages.SelectedItem != null)
            {
                var item = (Feedback)listViewMessages.SelectedItem;
                hlslEdit.SelectWordAt(item.Row, item.Column);
            }
        }

        private void Window_Activated(object sender, EventArgs e)
        {
            if (_documentController.FileHasChangedOutOfProc)
            {
                _documentController.FileHasChangedOutOfProc = false;
                if (MessageBox.Show("Your current effect file has been modified outside HLSL Editor. Do you want to load these changes?", "Reload needed", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
                {
                    _documentController.Reload();
                }
            }
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show(string.Format("EffectEd - by Thomas Vantroyen \n\nVersion: {0} - 01/02/2012", Assembly.GetExecutingAssembly().GetName().Version), "About", MessageBoxButton.OK, MessageBoxImage.Information);
        }

        private void MenuItemSaveAs_Click(object sender, RoutedEventArgs e)
        {
            _documentController.SaveAs();
        }

        private void MenuItem_Click_1(object sender, RoutedEventArgs e)
        {
            Close();
        }

        private void MenuItem_Click_2(object sender, RoutedEventArgs e)
        {
            var settingsDialog = new SettingsDialog();
            settingsDialog.Initialize(_codeAssistance);
            settingsDialog.SetSettings(_settings);
            var result = settingsDialog.ShowDialog();
            if (!(result.HasValue && result.Value)) return;
            _settings = settingsDialog.GetSettings();
            ApplySettings();
            SaveSettings();
        }

        private void SaveSettings()
        {
            Settings.Save(DataFiles.GetAbsolutePath("settings.xml"), _settings);
        }

        private void MenuItem_Click_3(object sender, RoutedEventArgs e)
        {
            ShowFileCompilerSettings();
        }

        private void ShowFileCompilerSettings()
        {
            var compilerSettingsDialog = new CompilerSettingsDialog();
            compilerSettingsDialog.Initialize(_codeAssistance);
            try
            {
                ShowFeedback(null, string.Empty, string.Empty);
                var compilerSettings = CompilerSettingsParser.GetCompilerSettings(hlslEdit.Text);
                if (compilerSettings == null) compilerSettings = _settings.DefaultCompilerSettings;
                compilerSettingsDialog.SetCompilerSettings(compilerSettings);
                var result = compilerSettingsDialog.ShowDialog();
                if (result.HasValue && result.Value)
                {
                    hlslEdit.Text = CompilerSettingsParser.SetCompilerSettings(hlslEdit.Text,
                                                                               compilerSettingsDialog.GetCompilerSettings());
                }
            }
            catch (Exception exception)
            {
                ShowFeedback(new List<Feedback>() {new Feedback(exception.Message)}, string.Empty, string.Empty);
            }
        }

        private void CommandBinding_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
        {
            _documentController.Save();
        }

        private void CommandBinding_CanExecute(object sender, System.Windows.Input.CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        private void FindCommandBinding_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
        {
            OpenFindWindow();
        }

        private void OpenFindWindow()
        {
            if (_searchWindow == null) _searchWindow = new SearchWindow() {Editor = hlslEdit};
            _searchWindow.Show();
            _searchWindow.Focus();
        }

        private void FindCommandBinding_CanExecute(object sender, System.Windows.Input.CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        private void MenuItem_Click_4(object sender, RoutedEventArgs e)
        {
            OpenFindWindow();
        }

        private void MenuItem_Click_5(object sender, RoutedEventArgs e)
        {
            hlslEdit.TextEditor.Undo();
        }

        private void MenuItem_Click_6(object sender, RoutedEventArgs e)
        {
            hlslEdit.TextEditor.Redo();
        }

        private void MenuItem_Click_7(object sender, RoutedEventArgs e)
        {
            MessageBox.Show(string.Format("The cursors current hierarchical position in the expression tree is: \n\n{0}\n\nThis value or a part of it can be used as a ContextExpression in the CodeAssistance.xml file to add code completion data to this particular location in HLSL code. It's allowed to omit identifiers from this ContextExpression for more general behavior or when they simply are not required to identify this location.",
                              GetUserFriendlyHierarchy()), "Context", MessageBoxButton.OK, MessageBoxImage.Information);
        }

        private string GetUserFriendlyHierarchy()
        {
            var hierarchy =
                new Parser().Parse(hlslEdit.Text).TreeRoot.FindExpressionAt(hlslEdit.TextEditor.CaretOffset).GetHierarchy();
            hierarchy.Remove("SingleToken"); // puzzles the user: singletoken is always the last item in the hierarchy and represents the token the cursor is at, but the user wants to
                                             // see the context it is in, not the exact word or token it is at.
            while (hierarchy.Contains("Sequence")) hierarchy.Remove("Sequence"); // puzzles the user: these are technical expression, not functional
            while (hierarchy.Contains("TokenChain")) hierarchy.Remove("TokenChain"); // puzzles the user: these are technical expression, not functional
            return string.Join(".", hierarchy);
        }

        private void MenuItem_Click_8(object sender, RoutedEventArgs e)
        {
            Compile();
        }

        private void MenuItem_Click_9(object sender, RoutedEventArgs e)
        {
            ShowFileCompilerSettings();
        }

        private void CompileCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            Compile();
        }

        private void CompileCommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        private void Window_DragEnter(object sender, DragEventArgs e)
        {
            e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None;
        }

        private void Window_Drop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                var filePaths = (string[])(e.Data.GetData(DataFormats.FileDrop));
                if (filePaths.Any())
                {
                    _documentController.Load(filePaths.First());
                }
            }
        }

    }
}
